- web6047 - (2021/09/10(金) 現在、システム調整中のため、一部の表示がおかしいかもしれません)

[横幅 1024px以下]

web6047 2022年

プログラミングやRPG(作るほう)が好きな人の日記

個人的な趣味(プログラミング、イラスト、電子回路)のページです。

自身のパソコンのやりすぎに対する管理の表の公開、

最近見ているアニメや映画と 買い物の簡易なリスト、

それから日記を掲載しています。日記が主体です。


パソコン使用時間管理の公開:   パソコン使用時間管理

この表は、このウェブページの管理人のパソコンの使用時間を管理・制限するためのものです。

このようなプライベートなことを公開して、ちょっと恥ずかしいようなところもありますが、公開することでうまくいっているので良しとしています。





































NO PC WEEK に代わる PC 使用制限のしくみ(新β2版)
No. A1.
開始時
運動
A2.
勉 強
1問
A3.
終了時
運動
H1. 予定
作業内容
H2. 予定
作業詳細
判定×の理由
B. 実際
開始時刻
C. 予定
使用時間
(当日限度)
D. 予定
終了時刻
E. 実際
終了時刻
F. 実際
使用時間
G1. 判定
◎ 9分以下
○ 10分~19分
△ 20分~29分
× 30分以上
569
RPGのメニュープログラム 試作
+webドキュメント作成
15:31 1:40 17:11


568 × RPGのメニュープログラム 試作
+webドキュメント作成
21:23 1:40 23:00 23:00 1:37
運動
567 × RPGのメニュープログラム CSS:objec t-fitについて 19:35 1:40 21:15 21:15 1:40 (お試し中 2h越えたら×)◎
夕食
566 × RPGのメニュープログラム CSS:objec t-fitについて 16:54 1:40 18:34 18:34 1:40 (お試し中 2h越えたら×)◎
間食ほか
565 × RPGのメニュープログラム CSS:objec t-fitについて 15:03 1:30 16:33 16:33 1:30 (お試し中 2h越えたら×)◎
※体調不良により休職、それにともない、生活環境が変わり、この表の管理も一時 未記入になりました。
 勉強したり、運動、気分転換や家事ができるものなので、再開します。
564 × × RPGのメニュープログラム 試作
+webドキュメント作成
20:10 1:30 21:40 21:40 1:30 (お試し中 2h越えたら×)◎
夕食
563 × × RPGのメニュープログラム 試作
+webドキュメント作成
18:00 1:30 19:30 19:30 1:30 (お試し中 2h越えたら×)◎
買い物
562 × × RPGのメニュープログラム 試作
+webドキュメント作成
15:15 1:30 16:45 16:45 1:30 (お試し中 2h越えたら×)◎
561 ×
RPGのメニュープログラム 試作
+webドキュメント作成
21:10 1:30 22:40 22:44 1:34 (お試し中 2h越えたら×)◎

この表の意図:

多くの人はパソコンのやりすぎやネットゲームのやりすぎには困っていると 思います。

参考に言うと、この表を使う前の私は 1 回の PC 使用時間がノンストップで 17 時間というときもあったし、平均で言うと毎日 9 時間はやっていたと思います。

この表を使ってパソコンの使用時間を 事前に決めてネッ ト上に公開 することで、パソコンのやりすぎを防止できたら、と思います。

また、数年前から考えてきましたが、そういう徹夜とか長時間作業をするよ りも、昼間の短時間作業のほうが生産性は高いのではないかと思います。そういう意味でも期待しています。

※以前は NO PC WEEK と称してパソコンを使用しない期間を設けることでやりすぎに対処してきましたが、もっと具合の良い方法はないかと考え、この表を使うようになりました。


臨時お試し(2022年1月8日~)

最近、状況が以下のように少し変化していて…

  • 23時で電源が切れる自動制御がとても強制的で、うまくいっている。
  • 23時以降まったくできなくなり、使用時間が減ってしまった。
  • 最近、通常のPC作業の他に、「PC-9801でのアセンブラ学習」が加わったので、さらに時間が足らなくなった。

…これまでのやり方だとうまくいかないところがあるので、お試しでやり方を少し変更します。

お試しで以下のようにします。

  • 基本的に何時間でもやってよい。
  • ただし、1.5h(または2.0h)ごとに区切り、区切りの際に15分以上 別のことを行う。
    行った内容は、参考のために記録と記録の間に記載する。
  • 使用可能時刻は、昼12:00~夜23:00のあいだ。(自動的に電源制御される)
  • 1.5hの開始時と終了時にこれまでどおり、開始時:運動、勉強、終了時:運動 を行うこと。
  • 以上は平日は火木金のみとし、月水は以下のようにする。
    月水は、「PC-9801でのアセンブラ学習」について基本的に本だけで学習する。
    しかし、必要な場合は1項目に限ってPC-9801の電源を入れて良い。

これがうまくいかない場合は、以上を取り消しします。

(「1.5h(または2.0h)ごとに区切り、」というのが難しいんじゃないかと思う…)

中途結果

  • これまでは 1.5h や 3, 4h の時間制限があったが、このお試しは時間が限られていないので、少し気持ちが楽になって良い。「余計なことで時間を使ってしまった~」というのが無い。


記入の規則:

  • 日付は表示していません(私の生活パターンをすべて知らせるのはよくないから)。しかし、白と灰色の色分けは、同じ色の連続 で同じ日を表しています。
  • 左端の「A1. 運動」、「A2. 1問」、「A3. 運動」について
    この表の目的とは異なりますが、ついでとして、遊び100%の毎日を送るときでも勉強の習慣を忘れないために、たった1問で良い ので解くことにします。
    正直言うと毎回遊ぶ前に必ず1問勉強するのは心が折れそうです。でも慣れさえすれば…と思います。(追記:やってみると結構効果 的で役立っています)
    行ったら◎、行わなかったら× を記入します。
    2020年11月20日ぐらいから「A1. 運動」を追加しました。パソコンを行う前に運動することを強制するものです。腕立て伏せ10回とか腹筋10回とかです。
    (ホントだったら30回くらいはやりたいところですが、私は体の調子が悪いので、10回程度にしています)
    2021年6月26日「A3. 運動」を追加。PC使用の終了時にも運動する。
    A1はPC漬けによる筋力衰え対策の意味で、筋トレを行い、
    A3はPC作業でこった体をほぐす意味で、ラジオ体操やストレッチ体操を行う。
  • 右端の「G. 判定」について
    「D. 予定終了時刻」と「E. 実際終了時刻」を比べて、オーバーした時間によって判定を行います。
    ◎ 9分以下
    ○ 10分~19分
    △ 20分~29分
    × 30分以上 (オーバー理由を記載する。理由の統計を取れば何が問題なのか把握でき、改善しやすいです)
  • 平日の 1.5h、2.0h の PC 使用の連続が負担になっているので、週のスケジュールは下記のようにする。
    月水で PC 使用しないことでうまいぐあいに「体力回復」されて、「生活」がうまく回り、プログラミング以外の「創作活動」(イラスト等)に時間が取れることを期待します。
    月:0h
    火:1.5h
    水:0h
    木:1.5h
    金:1.5h
    土:5hまで(休日で、翌日も休日)
    日:3hまで(休日で、翌日平日) ←早起きなおかつ(日々の買い物とかじゃなく)散歩するなら 3h やって良い
    (※2021年2月2日:週の各時間を調整しました)
    (※2021年12月12日:電源タイマーで23時で終了することに期待できるので土日の各時間を1時間ずつ増やしました)
  • ×、△、"記録しなかった長時間作業" が多いと思ったら、バランスをとるために、NO PC WEEK※ を1週間実施する。
    NO PC WEEK とは私自身の健康のために私のパソコンの使用を制限する期間です)


ちなみに、分単位で記録を取ったりして、だいぶマメに見えるかもしれませんが、Windows の日本語入力(MS-IME)で「いま」と入力し、 変換 キーを押さずに ボタンを押すと現在の時刻になります。道具の便利さが人をマメに見せるのかもしれません。

例外事項:

  • 「E. 実際終了時刻」のあと、プログラミングの場合のみスッパリ終了しないで、今後のプログラミングの方針をテキストファイルに書くのは OK にしています。


「スーパーPC WEEK」:

連休中(3連休以上)に、NO PC WEEK をオフにして好きなだけパソコンを使ってよいとする期間を、「NO PC WEEK」に対して「スーパーPC WEEK」と言う。

ただし以下の決まりを守ること。

  • その日に自由にパソコンを使ってもよいが、1回あたり、いつものように表に記入すること。(開始時運動、勉強1問、終了時運 動も行うこと)
     
  • 1回ごとに、掃除、炊事、洗濯や、別の趣味など、まとまった作業を はさむこと。
     (理由: 1回1回がが連続してつながると回を分けている意味がなくなるから)
  • 24時に就寝するよう努めること。(強制ではないが、つとめること)
     
  • 連休の最終日は通常通りとする。
     
  • 1回の使用時間は2時間を最大とすること。

なお、表の中央やや右寄りの「C. 予定 使用時間(当日限度)」列の "当日限度" には UNLOCK と記入する。


中途結果(2022/4):

(この記事は作成途中です)

現在固まってきた運用の内容をここにまとめます。

個人的ではありますが、自分にとって効果があると思っているものです。

ご参考になれば幸いです。

効果 大:

昼12時に電源供給が開始、夜11時に電源供給が終了するしくみ

電源オンオフタイマーを使用して、パソコンの電源タップへの電力の供給を制御します。

これにより、時間になるとマルチディスプレイの外付けの大きなディスプレイの電源が切れたり、オンされたりします。


パソコンの電源を入れた後、1.5Hで電源が切れる(休止状態になる)しくみ

1.5Hと1.5Hのあいだは15分以上別のことを行うルール

効果 中:

パソコン使用の前後に運動をする。

その他 続けていること:

パソコン使用の前に勉強の問題を1問解く

自分の家族にすすめたい方へ:


(これは イラス トAC の無料素材です)

パソコンのやりすぎやネットゲームのやりすぎは社会問題にもなっているので、「うちの子についてなんとかしないと…」と思っている ご家族の方は多くいらっしゃると思います。

私の両親も過去に私について問題にしていました。学校へ行かず、毎日朝までパソコンに向かい、 悶々としていたんです。


この表はその家族が困っていたときから 30 年後に、私が自分で必要を感じて作ったものです。

私は今 一人暮らしをしていて、自分で生計を立てる中、パソコンにおぼれた生活をすると、生活がうまく回らなくなるんです。

具体的には、

  • 人前で疲れた顔を見せてしまい、人間関係がうまくいかなくなる。もっと具体的には、職場、とこや、お店のレジ、歯医者。
  • 掃除、洗濯、炊事が後回しになり、実質、それらを行う時間がなくなってしまう。深夜遅くや翌日に回したり、行わなかったりす る。

これらを改善するために表を作りました。


でも、このような必要にせまられて「自分の動機で始めた場合」と、「人からすすめられて始めた場合」とでは、結果が異なると思いま す。

自分の切実な動機で始めたなら自分から進んでこの表を活用すると思いますが、外から押し付けられたものはなかなか定着しないもので す。

あまり適当なことは言えませんが、「中途結果」タブの中の青い部分で 書いたことは、本人にとって得になることなので、「ときどき休憩して、他のあの趣味やってみたらどうだ?」とか「ときどき休憩したほ うがプログラミングの質が上がるって話だぞ?」という形ですすめてみたらどうでしょうか。(それでも最終的には自立してもらうことは 必要だと思いますが)


私が両親を困らせていたときに、突然、外へ一人で出て行って、一人暮らしを始めたり、接客業を始めたり、いくつか資格取得したりと いろいろ行えた理由というのは、正直言ってわかりません。(※しかし途中で失業して2度、実家に戻ったことがあります。1 回目は 21 才くらいのときに 5 年間、2 回目は 35 才くらいのときに1年未満、実家にいて、何もしてなかったり働いたりしていました)

私が両親を困らせていたのは 16 才 ~ 20 才くらいの学生のころですが、そのころ家族と私自身と友人たちがみんなそれぞれ、私の生活について心配したり困ったり悩んだり、あの手この手を試したりしていました。そう いう煮詰まったような状況が運命をそのように(解決の方向へ)動かすのかもしれません。運命がどうの というのは変ですが、そのくらいのことしか言えません。何かしら取り組む必要があるということですかね。


この社会問題はクリアーすべきものみたいです。



最近観ているアニメ: 最近観ているアニメ
日 付
(上ほど新しい)
タイトル 無料配信
配信日
公評価 私 評価
2021/7/15~視聴中 ドラゴンボール(リンクは Yahoo GYAO で検索)
冒険・格闘技/1984年週刊少年ジャンプ/各話25分程度
月・水・金・・ ★★★★
4.8
★★★★
4.5
最近観た映画: 最近観た映画

このところ、Yahooの無料映画サービス「GYAO」で映画をいっぱい観ています。

日 付
(上ほど新しい)
タイトル 無料配信
(日付まで)
公評価 私 評価
2022年3月 10x10 テン・バイ・テン
(リンクは無料映画のGYAOで検索)
この映画を途中まで見ていると、いやな気持にされてそれで終わるんじゃないか、という心配が出てきますが、おおむね大丈夫な映画です。後味は悪くないです。
宗教の「規律」にしばられていることを描いた映画として、有名な「セブン」を思い出しました。「それ」を行う動機がよく似ています。
「セブン」ほどすごい内容ではないですけどね。
オススメ度:中
までGYAOにて無料配信 2.9 3.0
2022年3月 タイムマシン
(リンクは無料映画のGYAOで検索)
前半と後半は別の物語だと思って観れば、不満は少しおさまります。
前半のエマという恋人が、後半でほとんど忘れられているのが疑問です。
オススメ度:低
まで 3.5 2.5
2022年3月 砂上の法廷【吹替版】
(リンクは無料映画のGYAOで検索)
キアヌ・リーヴス主演。
少年が父親を殺した殺人事件の裁判の話。
まぁおもしろかった。
オススメ度:中
までGYAOにて無料配信 3.9 3.3
2022年3月 トゥモロー・ワールド
(リンクは無料映画のGYAOで検索)
ジュリアン・ムーア助演。
後半の戦争(蜂起)シーンは最高にリアルだった! 久しぶりに映画らしい映画を観たと思う。
オススメ度:高
までGYAOにて無料配信 3.4 4.2
2022年3月 ジュピター20XX
(リンクは無料映画のGYAOで検索)
木星の衛星エウロパにクジラがいるということで、それを研究調査するために二人の宇宙飛行士が飛び立った。ところが小石程度の隕石が宇宙船に衝突し…という話。数年間、一人、小さな船室で生活することになります。間近の金星がリアルで壮大でした。
異色作だけど悪くない映画でした。
オススメ度:中
までGYAOにて無料配信 3.0 3.0
2022年3月 シックスデイ【吹替版】
(リンクは無料映画のGYAOで検索)
アーノルド・シュワルツェネッガー主演。
ある日家に帰ると、自分の誕生パーティが開かれていて、そこにはもう一人の自分がいた!!本物であるはずの自分が抹殺される!という話です。
敵役が最後の最後まで、これでもかとしつこくて。
まぁ面白かったです。
オススメ度:中
までGYAOにて無料配信 3.8 3.5
2022年3月 バイオハザード:ザ・ファイナル【吹替版】
(リンクは無料映画のGYAOで検索)
博士とその病気の娘、博士とその病気の娘、同シリーズで同じような話がかぶっていて、矛盾はないのかなと疑問に思います。
面白かったです。
オススメ度:高
までGYAOにて無料配信 3.2 3.6

最近買ったもの: 最近買ったもの
日 付
(上ほど新しい)
タイトル 公評価 私 評価
2022/3/1 パソコンの CPU の解説書です。
1994年に刊行された本で、アマゾンで中古で購入しました。
マイクロプロセッサ(CPU)について基本の動作を知っている方が対象の本なので、難しいですが、読むと、みなさんの高評価の通り、486 CPU のしくみをとてもよく理解しながら読み進められます。
Intel 486 CPU は、現在の Intel core i シリーズの基になった CPU であり、技術的に現在にも通用するところがあると思います。学んだことが無駄にならず結構良いと思います。

「486 以上の x86 系 CPU の技術的なところ」と、「情報処理の技術的なところがある程度わかっている人」の橋渡しをしてくれる本だと言えます。
この「はじめて読む 486」が教えてくれていることの内容は結構 難解な内容で、それがもし intel の難しい技術書(見たことありませんが) でしか知ることが出来ないとしたら、理解はほとんど無理なんじゃないか…
そう言えるくらい、難しい内容を分かりやすく説明してくれています。人気があるのも当然です。(これを書いている今は、この本の半分くらいまで読みました)
4.0 4.5
2021/8/7 曲:アンインストール(リンクは Apple Music の当該ページへ)
2007年の「ぼくらの」というロボットアニメの主題歌
歌詞を読みましたが、「突然現れた新しい現実に対し、受け入れ、や るっきゃない」と言っているのかなぁ…
5.0 4.0
2021/7/9 ゲーム:ザ・トリロジーズ -T&E SOFT / XTAL SOFT COLLECTION-(リ ンクは EGG の当該ページへ)
PCレトロゲームのセット。RPG開発の研究のために予約で購入。
当初「2021年春発売予定」でしたが、コロナウイルス感染拡大の影響で、「2021年夏」に延期され、さらに「2022年2月発売」、「2022年3月」、「2022年4月」と繰り返し延期されています。
私は急いでいないんで、いいんですけどね。
最近やっと「4月上旬発売決定」となりました。

2022年4月17日追記
プラスチックケースはたしかに当時のあの豪華な感じを体感できました。なつかしくて嬉しかったです。

4.0


自己紹介: 自己紹介

47才、男、B型(BB)

電子機器の基板を製造する工場で、派遣で働いています。

プログラミングが好きで小学校5年生のころからずっと続けています。

ページ上にていろいろ才能(少々 粗削りな才能)を発揮していると思いますが、なるべく自 分だけで終わらないようにといつも思っています。

いろいろ厳しい考え方も持っていますが、厳しすぎないように周囲の人々とのバランスも考えていま す。

たとえば、著作権について私はだいぶ細かく考えていますが、私以外の人について 特に厳しく言ったり批判的に見たりはしません。

著作権は難しいし、ちょっとぐらい いいじゃないか、という人間らしい気持ちを肯定したいからです。

■趣味: プログラミング、ゲーム音楽を集めて聴く、イラストを描く、映画、アニメ、ガンプラ (興味の強い順)

■将来の夢は5つくらい持っていますが、生きてる間に実現できそうにありません。


■私が使っているペンネーム(ハンドルネーム)は新しい順に

ペ ンネーム(ハンドルネーム) いつごろ 場 所
daikei 現在(少) Yahoo Japan
d_kawakawa 現在(多) どこでも
cookiepurinman、cookiepudingman ちょっと前 Twitter
かわ、kawa 30年前 パソコン通信

※基本的には d_kawakawa とお呼びください。


■私への連絡手段:

ごくまれにしかツイートしませんが、私の twitter アカウントはこちらです。

https://twitter.com/cookiepudingman

何かメッセージをくれれば、返事すると思います。

返事がないときはメッセージに気付いていないんだと思います。そのときはすみません。


特設ページ群:

PC-9801 臨時特設ページを開く    …特設ページ作りましたが、更新は止まっています。

「夢幻の心臓II」特設ページを開く …また3日坊主になるかも。

電子機器組立て2級 暗記ツール …国家検定の電子機器組立て2級の暗記ツール(基板上配置)です。1級でも役立つと思います。


日記: 日記  (日記がこのサイトのメインコンテンツです。上の記事ほど新しい記事です)

2022/4/30(土)

エラー修正

長らくエラーのままにしていた下図の赤枠で示したリンクを直しました。

この画像をクリックするとその場所へ飛びます。

この修正に関連して、他でエラーを出していた部分(プログラムリスト表示関係)も直っているかもしれません。



2022/4/17(日)

ココアパウンドケーキ

安上りで、甘くない間食(おやつ)が必要だと思って、最近はホットケーキミックスを使って、パウンドケーキを作っています。

詳しいレシピはこちらの別ページを見てください。


fig.
▲トースターで焼いている
生地を作って、トースターで焼いているところです。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲こんがりと焼けた

15分焼いたところです。

割れ目ができて、それっぽい感じだ。

(ほんとなら、割れないように工夫をするんだと思います)

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲キチっとした焼き上がり

包丁で切り分けて、はちみつを付けながら食べます。

はちみつは砂糖と違って、「不溶性のグルカン」が発生せず、プラーク(細菌の塊)ができにくい。

そのため砂糖と比べると虫歯になりにくいんだそうです。

ここの説明より(リンクはどこかの歯医者さんページへ)

左の画像リンクをクリックすると 画像を拡大 します。



そんなに手間をかけずに、良いものが出来るんだなぁ、ということでした。


(訪問者のどんなニーズと この記事がつながるか)


2022/4/12(火)

電子機器組立て2級 暗記ツール ダウンロードページ 設置

fig.
▲暗記ツール

電子機器組立て2級 暗記ツール をダウンロードするためのページを作成しました。

左の画像リンクでそのページを開きます。

以前公開したときは、その基板画像の著作権を気にしていましたが、少し考え直して普通に公開することにしました。

今年は私も1級の試験を受験することにしています。



(訪問者のどんなニーズと この記事がつながるか)


2022/4/11(月)

RPG コマンドメニュー その5

Mini-NAVIGATION
RPGメニューを作る1
基本のプログラムからはじめて、サブメニューを表示するまで
Step 1~8
RPGメニューを作る2(記事としては4)
メニューの項目に、付加情報(装備中のe記号や、店の価格)を表示。
Step 9、10、11
RPGメニューを作る3(記事としては5)
メニューの項目を段組表示する
Step 12、13

現在作成途中の記事です。

RPG コマンドメニュー Step. 12

煩雑な処理を改善

先月の 2022/3/26(土)の日記、「RPG コマンドメニュー その4」の記事に掲載した Step.11 タブの中、

変更点2にて、「キャラクターのどうぐのメニューで、装備中を意味する「e」の文字を選択肢中に表示した影響で、処理が煩雑になった」と書きました。(下図に抜粋、行番号は 288 付近)

この図の変更点2が示す部分(水色部分)では、douguMihon がほしいわけですが、douguMihonz から引っ張ってくるために、少し遠回りになっています。

1行目:douguMenu.selectedIndex を添え字にして character.dougus 配列から どうぐ を取り出し、その name プロパティを取り出している。

2行目:その name をキーにして douguMihonz から引っ張ってくる。

(以下の緑色部分は経験のある方向けの記述で、まだこれから勉強していく方にとってはこれから理解できるプログラミングの面白いところです)

水色部分の1行前の while( await douguMenu.select() ) { のあとで、水色部分の処理で どうぐ を参照するのは決まりきったことなのに、Menu クラスは selectedIndex(選択された項目の並び順) と selectedMenuTitle(選択された項目の表示文字列)しか用意していないという、後のプログラムに対する不親切な状態(設計)となっています。selectedItem(選択された項目が、ロジックではなく意味的に指す(後で使うであろう)オブジェクト)を用意してくれても良いんじゃないか、と思うわけです。

もちろん、オブジェクト指向的に考えれば、あるクラスが自分の役割を超えて、何かを特別に用意するというのは、依存関係が生まれそうで(のちの開発で 禍根(読み:かこん)を残しそうで)、難しいところですが、まぁ、人間の考える親切というのは時に理屈に合わないということもあるでしょう。この辺は議論の余地のあるところです。(えらそうに言っていますが、オブジェクト指向について私が無知なだけ(経験があるつもりで経験が浅いだけ)かも)

今回の Step.12 では、この煩雑な処理(不親切な設計)を改善しています。

1行目:douguMenu の selectedItem の name プロパティをキーにして、douguMihonz から取り出している。

このように簡潔になりました。

プログラミングのテクニックとして、ある問題についてどのように改善を行ったのか、具体的に見てほしいと思って改善したことを伏せずに明示することにしました。

しかし、よく使う変数の変数名を変更したので、下記の変更点はだいぶ多くなってしまいました。

62	baloonLength : 1, baloonColor : springgreen, baloonShiftRight : 40, baloonShiftTop : 4
<span style="color: #006600;">
<strong>変更点1(1/16) </strong>目的:煩雑な処理を修正<br>
今まで Menu はその定義の中で、「メニューの選択肢」として items を設定していましたが、これを itemTitles に名前を変更しました。<br>
それに合わせて、その他の item と名のつく変数も itemTitle に変更しています。(全16か所)</span>

70 baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(2/16)</strong>89行、変数名 item → itemTitle</span>

270	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(3/16)</strong>263行、変数名 item → itemTitle</span>

294	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(4/16)</strong>287行、変数名 item → itemTitle</span>

297	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(5/16)</strong>290行、変数名 item → itemTitle</span>

309	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(6/16)</strong>302行、変数名 item → itemTitle</span>

316	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(7/16)</strong>309行、変数名 item → itemTitle</span>

320	baloonLength : 1, baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(8/16)</strong>313行、変数名 item → itemTitle</span>

335	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(9/16)</strong>328行、変数名 item → itemTitle</span>

354	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(9/16)</strong>328行、変数名 item → itemTitle</span>

494	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(9/16)</strong>328行、変数名 item → itemTitle</span>

503	baloonColor : springgreen, baloonLength : 1.5
<span style="color: #006600;">
<strong>変更点1(10/16)</strong>347行、変数名 item → itemTitle</span>

526	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(11/16)</strong>456行、変数名 item → itemTitle</span>

566	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(12/16)</strong>468行、変数名 item → itemTitle</span>

579	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(13/16)</strong>539行、変数名 item → itemTitle</span>

610	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(15/16)</strong>580行、変数名 item → itemTitle。<br>
また、下行に item についてあらためて追加</span>

623	baloonColor : springgreen
<span style="color: #006600;">
<strong>変更点1(16/16)</strong>593, 594行、変数名 item → itemTitle</span>

67	baloonColor : cyan, baloonShiftRight : 28
<span style="color: #000099;">
<strong>変更点2(1/5)</strong> 目的:煩雑な処理を修正<br>
別の意味で items という配列をあらためて設置しました。<br>
itemTitles はメニューに表示するための文字列の配列ですが、items はメニュー選択後に実際に使用するデータの配列です。</span>

279	baloonColor : cyan
<span style="color: #000099;">
<strong>変更点2(2/5)</strong>272行、items を設置</span>

298	baloonColor : cyan, baloonReverse : true
<span style="color: #000099;">
<strong>変更点2(3/5)</strong>291行、items を設置</span>

321	baloonColor : cyan, baloonReverse : true, baloonLength : 0.5
<span style="color: #000099;">
<strong>変更点2(4/5)</strong>314行、items を設置</span>

356	baloonColor : cyan, baloonReverse : true
<span style="color: #000099;">
<strong>変更点2(5/5)</strong>349行、items を設置</span>

529	baloonColor : cyan, baloonReverse : true, baloonShiftRight : 216
<span style="color: #000099;">
<strong>変更点3</strong><BR>
items が設定されているなら参照して selectedItem へセットする。<BR>
items が設定されていないなら null とする。
</span>

302	baloonReverse : true
<strong>変更点4(1/3)</strong> 目的:煩雑な処理を修正<br>
変更点2により、「メニューの選択後に実際に使用するデータ」を取り出すまでが簡潔になりました。

324	baloonReverse : true
<strong>変更点4(2/3)</strong>317行、「メニューの選択後に実際に使用するデータ」を取り出すまでが簡潔になりました。

361	baloonReverse : true
<strong>変更点4(3/3)</strong>354行、「メニューの選択後に実際に使用するデータ」を取り出すまでが簡潔になりました。

506
<strong>変更点5</strong> タイトルのほうが長い場合は、タイトルの長さをメニューウィンドウの幅として採用する。


下の大きな緑のボタンを押してプログラムを実行し、Zキーでトップメニューを開いて、「どうぐ」→「リサリー」と進めると、「まほうのステッキ 」と表示されます。

以下の JavaScript を新しいウィンドウで実行

<html><!--ESCAPEPROCESS-->

<head>

<title>Step12</title>

<meta content="text/html; charset=UTF-8" http-equiv="content-type">

<script>

console.clear();

</script>

<script src="touchjs/touch.js"></script>

<script>


//#####


function onloadx() {

app = new App( document.getElementById( "testCanvas" ) );

app.start();

}

/*

皆の者、聞いてくれ。


魔王は、突然この世にあらわれ、

いくつかの国や街、村を滅ぼした。


もうすでにわが国の軍隊は何度も魔王に挑み、敗北している。


兵士たちよ、皆の者の中に、

われこそはと名乗りを上げ、

魔王を倒してくれる者はおらぬか?



名乗りを上げますか?

*/


//##### App #####


class App {

//---Model---

constructor( canvas ) {

this.cc = canvas.getContext( "2d", { alpha : false } );

this.cc.canvas.width = 512;

this.cc.canvas.height = 448;


//画面上の要素を目的別に参照するための配列群

//(1つの要素が複数の目的に属しても良い)

this.elementz = {

menus : new Array(), //メニュー要素

draws : new Array(), //描画できる要素

}


this.currentElement = this;

this.animationFrameId = null;

this.cfg = {

charsize : 16,

}


//---storyData


this.douguMihonz = {

"やくそう" : {

storyOfCamp : async function() {

let dousuruMenu = this.openMenu( {

title : "どうする",

itemTitles : [

"つかう",

"わたす",

"すてる",

],

items : null,

} );

while( await dousuruMenu.select() ) {

alert( dousuruMenu.selectedItemTitle );

}//while

this.closeMenu();

}.bind( this ),//storyOfCamp()

},//やくそう

}//douguMihonz


this.playerCharacterz = {

"キエルン" : {

dougus : [

{

name : "やくそう",

},

{

name : "やくそう",

},

{

name : "やくそう",

},

{

name : "ひほう",

},

],

},

"トンキー" : {

dougus : [

],

},

"リサリー" : {

dougus : [

{

name : "まほうのステッキ",

equipped : true,

//equipped(読み:イクウィップト、意味:装備中)

},

],

},

}//playerCharacterz


//tweak. requestAnimationFrameのために

this.frame = this.frame.bind( this );

/*

JavaScriptの

requestAnimationFrame()は関数を1つ受け取る。

class Appのframe()メソッドをその関数として渡した場合、

そのままだと

frame()メソッド中のthisがwindowオブジェクトになってしまう。

ここでbindしておくことでthisをあらかじめ定義する。

*/

}

start() {

//既存のキーイベントをバックアップ

this.onkeydownBak = window.onkeydown;

this.onkeyupBak = window.onkeyup;

//キーイベント

window.onkeydown = function( e ) {

//基本的にカレント要素に 押されたキー番号を渡す

this.currentElement.keyExec( e.which );

}.bind( this );

window.onkeyup = function( e ) {

}.bind( this );

//animationFrameの開始

this.frameTimestampBak = 0;

this.frame( 0 );


//タッチ対応 開始

touch.start( app.cc.canvas, 90, 88 );

}

stop() {

//タッチ対応 終了

touch.stop();


window.onkeydown = this.onkeydownBak;

window.onkeyup = this.onkeyupBak;

window.cancelAnimationFrame( this.animationFrameId );

}

frame( timestamp ) {

if( timestamp - this.frameTimestampBak >= 100 ) {

this.frameTimestampBak = timestamp;


this.draw( this.cc );

}

this.animationFrameId = requestAnimationFrame( this.frame );

}

openMenu( menuDefinition ) { //definition(意味:定義)

/*

メニューを開く手続き

Menuを作成し、目的別配列にセットし、カレントに設定する。

カレントとは、画面上に複数ある要素のうち、

キー入力等を受け取る現在アクティブな要素のこと。

*/

let menu = new Menu( menuDefinition, this );

//tweak. サブメニューである場合、適切な位置を自動設定する。

let parentMenu = this.elementz.menus[ this.elementz.menus.length - 1 ];

if( parentMenu ) {

menu.gx = parentMenu.gx;

menu.gy = parentMenu.gy;

//check. 親にタイトルがある場合、タイトル分下へ

if( parentMenu.definition.title ) menu.gy += this.cfg.charsize;


let tweakCx, tweakCy;

if( parentMenu.definition.ch / parentMenu.definition.cw < 1 ) {

//親メニューが横長

tweakCx = parentMenu.cursorX * parentMenu.definition.cellCw + parentMenu.cursorX + 1;

//式中の + parentMenu.cursorX は

//選択肢間の横方向スペース(spacing)の数を意味する。

tweakCy = parentMenu.cursorY + 1;

console.log( "サブメニュー位置決め", 1 );

} else {

//親メニューが縦長

tweakCx = parentMenu.definition.cw;

tweakCy = parentMenu.selectedIndex + 1;

console.log( "サブメニュー位置決め", 2 );

}


menu.gx += tweakCx * this.cfg.charsize;

menu.gy += tweakCy * this.cfg.charsize;


if( Utl.isContain( parentMenu, menu ) ) {

//見た目として子メニューが親メニュー内に含まれるとき

//少し右にはみ出させる

menu.gx = parentMenu.gx + parentMenu.gw - menu.gw + this.cfg.charsize;

console.log( "サブメニュー位置決め", 3 );

}

}//if


this.elementz.menus.push( menu );

this.elementz.draws.push( menu );

this.currentElement = menu;

return menu;

}

closeMenu() {

//一番手前のメニューを閉じます。


//menusから削除し、

//(menusの最後が一番手前のメニューであり、それを閉じたいからpopでよい)

let menu = this.elementz.menus.pop();

//そのmenuをdrawsからも削除する。

this.elementz.draws.splice( this.elementz.draws.indexOf( menu ), 1 );


//カレントについて

if( this.elementz.menus.length )

//1つ前のmenuにカレントを変更する。

this.currentElement = this.elementz.menus[ this.elementz.menus.length - 1 ];

else

//メニューが他に無いときはカレントはこのアプリとする。

this.currentElement = this;

}

//---View---

draw( cc ) {

cc.fillStyle = "black";

cc.fillRect( 0, 0, cc.canvas.width, cc.canvas.height );

cc.font = ( this.cfg.charsize - 1 ) + "px monospace";


//グリッド

cc.strokeStyle = "darkblue";

for( let y = 0; y < cc.canvas.width; y += this.cfg.charsize ) {

for( let x = 0; x < cc.canvas.width; x += this.cfg.charsize ) {

cc.strokeRect( x, y, this.cfg.charsize, this.cfg.charsize );

}

}


//「描画できる要素」を描画

for( let element of this.elementz.draws ) {

element.draw( cc );

}


//debug. グリッド2

if( 0 ) {

cc.strokeStyle = "RGBA(0,0,0,.125)";

for( let y = 0; y < cc.canvas.width; y += this.cfg.charsize ) {

for( let x = 0; x < cc.canvas.width; x += this.cfg.charsize ) {

cc.strokeRect( x, y, this.cfg.charsize, this.cfg.charsize );

}

}

}

}//draw()

//---Controller---

keyExec( key ) {

//キーを処理する(App)

if( key == 90 ) { //z key

this.storyOfCamp();

}

else if( key == 88 ) { //x key

}

else {

console.log( "app:", key );

}

}


//---DATA---

//RPGイベントスクリプト(=storyと呼ぶ)


async storyOfCamp() { //中で await を使う関数は async を付けること

//RPGイベントスクリプト:

//フィールド上で開くコマンドメニュー

let commandMenu = this.openMenu( { //トップメニュー作成

cx : 3,

cy : 3,

title : "コマンド",

itemTitles : [

"しらべる",

"どうぐ",

"まほう",

"ひっさつ",

"つよさ",

"みせ(テスト)",

"Sample2",

],

items : null,

} );

while( await commandMenu.select() ) { //トップメニュー選択待ち

/*

awaitでJavaScriptは一時停止します。

commandMenu.select()はPromiseをreturnしています。

そのPromiseの「処理完了」で一時停止が解除されます。


なぜwhileなのかというとそれはサブメニューに対応するためです。

whileを入れ子にするとサブメニューを表現しやすくなります。


async, await, Promise は少し難しいですが

ゲームでよく使うhitAnyKey()や、delay()を

簡単に実現できて便利なので覚えたほうが良いです。

*/

if( commandMenu.selectedItemTitle == "どうぐ" ) {

let characterMenu = this.openMenu( { //サブメニュー1作成

title : "だれの",

itemTitles : Object.keys( this.playerCharacterz ),

items : Object.values( this.playerCharacterz ),

} );


while( await characterMenu.select() ) { //サブメニュー1選択待ち

let character = characterMenu.selectedItem;

//check. どうぐをもっていない

if( character.dougus.length == 0 ) {

continue;

}


//どうぐリストに装備中を示すeの文字を付与する

let itemTitles;

let douguNames = Utl.getArrayOfKey( character.dougus, "name" );

let douguEquippedMarks = new Array();

//eの文字の有無を表す配列を作成

for( let i = 0; i < character.dougus.length; i++ )

douguEquippedMarks.push( character.dougus[ i ].equipped ? "e" : "" );

//どうぐの名前とeの文字を合成してメニューの選択肢にする

itemTitles = Utl.joinToRight( douguNames, douguEquippedMarks );


let douguMenu = this.openMenu( { //サブメニュー2作成

title : "どれ",

itemTitles : itemTitles,

items : character.dougus,

} );

while( await douguMenu.select() ) { //サブメニュー2選択待ち

let douguMihon = this.douguMihonz[ douguMenu.selectedItem.name ];

//check. 未定義

if( ! douguMihon ) continue;

if( ! douguMihon.storyOfCamp ) continue;

await douguMihon.storyOfCamp( characterMenu.selectedItem );

}

this.closeMenu();

}

//characterMenu.select()でキャンセルされるとここに来ます。

this.closeMenu();

}

else if( commandMenu.selectedItemTitle == "みせ(テスト)" ) {

this.storyOfShop( {

title : "どうぐや「セリーヌ・デ・イオン」",

shopItems : [

{ name : "やくそう", price : 10 },

{ name : "どうのつるぎ", price : 120 },

],

} );

}

}//while

//commandMenu.select()でキャンセルされるとここに来ます。

this.closeMenu();

}//storyOfCamp()

async storyOfShop( menuDefinition ) {


//shopItems の各要素の name と price を結合し、1つのメニュー選択肢にする

let names = Utl.getArrayOfKey( menuDefinition.shopItems, "name" );

let prices = Utl.getArrayOfKey( menuDefinition.shopItems, "price" );

prices = Utl.hans2zens( prices );

menuDefinition.itemTitles = Utl.joinToRight( names, prices, menuDefinition.title.length, "G" );


menuDefinition.items = menuDefinition.shopItems;


//お店メニューを実行

let shopMenu = this.openMenu( menuDefinition );

while( await shopMenu.select() ) {

let shopItem = shopMenu.selectedItem;

alert( shopItem.name + "は " + shopItem.price + " Gだ。" );

}

this.closeMenu();

}//storyOfShop()

}//App


//##### Utl #####


class Utl {

/*

汎用のユーティリティを集めたクラス。

このクラスの各メソッドはクラスメソッド(static を付けている)なので、

alert( Utl.han2zen( "test" ) ); //結果:"test"

のような書き方で、使用します。

*/

static getArrayOfKey( array, key ) { //keyに対応する値の配列を取得します。

/*

引数arrayの各要素はオブジェクトであることが前提です。

引数arrayの各要素のkeyプロパティからなる配列を返します。

*/

let result = new Array();

for( let object of array ) {

result.push( object[ key ] );

}

return result;

}

static han2zen( string ) { //文字列中の半角文字を全角文字に変換します。

//check.

string = String( string ); //数値の場合は文字列にする


let result = "";

let hanSta = "!".charCodeAt( 0 ); //文字コード中の半角開始位置

let hanEnd = "~".charCodeAt( 0 ); //文字コード中の半角終了位置

for( let i = 0; i < string.length; i++ ) {

let charCode = string.charCodeAt( i );

if( charCode >= hanSta && charCode <= hanEnd )

//半角は全角に

result += String.fromCharCode( charCode + 0xFEE0 );

else

//全角はそのまま

result += String.fromCharCode( charCode );

}

return result;

//上記は下記でも同じ結果となります。(正規表現とアロー関数式を使用)

//return s.replace( /[!-~]/g, c => String.fromCharCode( c.charCodeAt( 0 ) + 0xFEE0 ) );

}

static hans2zens( array ) { //配列中の半角文字を全角文字に変換します。

let result = new Array();

for( let element of array )

result.push( this.han2zen( String( element ) ) );

return result;

}

static getMaxLengthOf( array ) { //文字列の配列の中で、一番長い文字列の文字数を返す。

let maxLength = 0;

for( let element of array ) {

maxLength = Math.max( maxLength, String( element ).length );

}

return maxLength;

}

static joinToRight( array, notes, otherLength, unit ) { //文字列配列にメモを付ける(右寄せ)

/*

arrayの要素を左寄せ、notesの要素を右寄せにした文字列配列を返す。

arrayとnotesは配列であり、両者の数は等しいこと。

otherLength maxLengthについてotherLengthのほうが長いなら、otherLengthの長さを採用する。

unit noteに単位を付ける。

*/

//check. 引数が省略された

if( ! otherLength ) otherLength = 0;

if( ! unit ) unit = "";

//check. 間違えて文字列を指定している

if( typeof otherLength === "string" ) alert( "error at joinToRight()" );

//check. notes がすべて空文字列である

if( notes.join("") == "" ) return array;


let maxLength = this.getMaxLengthOf( array ) + 1 + this.getMaxLengthOf( notes ) + unit.length;

//途中の1は、少なくとも1つのスペースを開けるという意味


//check. otherLengthも考慮する

maxLength = Math.max( maxLength, otherLength );


let result = new Array();

for( let i = 0; i < array.length; i++ ) {

let element = array[ i ];

let note = notes[ i ];

let interval = maxLength - element.length - note.length - unit.length;

let spaces = Array( interval + 1 ).join( " " );

result.push( element + spaces + note + unit );

}

return result;

}//joinToRight()

static isContain( parent, child ) {

/*

parentの描画範囲がchildの描画範囲を含むときtrue

parent, child はそれぞれ、gx, gy, gw, gh を用意していることが前提。

*/

//check.

if( parent.gx == null || child.gx == null ) {

alert( "err: object has not gx or other axis at Utl.isContain()." );

return;

}

return child.gx >= parent.gx && child.gy >= parent.gy

&& child.gx + child.gw <= parent.gx + parent.gw

&& child.gy + child.gh <= parent.gy + parent.gh;

}//isContain()

}//Utl


//##### Menu #####


class Menu {

/*

Menu は、

「1つのメニューを表示し、カーソルで選択肢を選び、選ばれた選択肢を返す」

という単純な動きを行う。

*/


//---Model---

constructor( definition, app ) { //definition(意味:定義)

this.definition = Object.assign( { //assign()は第1引数を第2引数で上書きする

//初期値

cx : 0,

cy : 0,

}, definition ); //初期値に上書きする

/*

その他

cw //ウィンドウの横幅(単位=文字)

ch //ウィンドウの縦幅(単位=文字)

*/


this.appcfg = app.cfg;


//check. ch未指定時

if( this.definition.ch == null ) {

this.definition.ch = this.definition.itemTitles.length;

}

//check.

if( this.definition.title )

this.definition.ch ++;


//check. cw未指定時 自動計算

if( this.definition.cw == null ) {

//選択肢の中で一番大きい文字列幅を得る。

this.definition.cw = Utl.getMaxLengthOf( this.definition.itemTitles );

//check. タイトルについても考慮する

if( this.definition.title )

this.definition.cw = Math.max( this.definition.cw, this.definition.title.length );

}


//メニュー枠の内側のスペース(単位dot)

this.padding = this.appcfg.charsize / 2;


//画面上の描画位置とサイズ

this.gx = this.definition.cx * this.appcfg.charsize - this.padding;

this.gy = this.definition.cy * this.appcfg.charsize - this.padding;

this.gw = this.definition.cw * this.appcfg.charsize + this.padding * 2;

this.gh = this.definition.ch * this.appcfg.charsize + this.padding;


//カーソル位置

this.cursorY = 0;


//選択された選択肢番号と選択肢

this.updateSelectedIndex();

}//constructor()

updateSelectedIndex() {

this.selectedIndex = this.cursorY;

this.selectedItemTitle = this.definition.itemTitles[ this.selectedIndex ];

if( this.definition.items )

this.selectedItem = this.definition.items[ this.selectedIndex ];

else

this.selectedItem = null;

}

//---View---

draw( cc ) {


cc.save();


cc.translate( this.gx, this.gy + this.padding ); //tr1


//枠を描画

cc.fillStyle = "white";

cc.strokeStyle = "black";

cc.fillRect( 0, 0, this.gw, this.gh );

cc.strokeRect( 0, 0, this.gw, this.gh );


//タイトルを描画

if( this.definition.title ) {

let leftSpace = Math.floor( ( this.definition.cw - this.definition.title.length ) / 2 );

let titleStringX = this.padding + leftSpace * this.appcfg.charsize;

let titleStringY = this.appcfg.charsize - 2;

let titleW = this.gw;

let titleH = this.appcfg.charsize;

//タイトル背景

cc.fillStyle = "blue";

cc.fillRect( 0, 0, titleW, titleH );

//タイトル文字

cc.fillStyle = "white";

cc.fillText( this.definition.title, titleStringX, titleStringY );

cc.fillText( this.definition.title, titleStringX + 1, titleStringY );


cc.translate( 0, this.appcfg.charsize ); //tr2

}


//選択肢を描画

cc.translate( this.padding, 0 ); //tr3

cc.fillStyle = "black";

for( let i = 0; i < this.definition.itemTitles.length; i++ ) {

let gx = 0;

let gy = i * this.appcfg.charsize;

//check. カーソルを描画

if( i == this.cursorY ) {

let bak = cc.fillStyle;

cc.fillStyle = "#ff7";

let gw = this.definition.cw * this.appcfg.charsize;

let gh = this.appcfg.charsize;

cc.fillRect( gx, gy, gw, gh );

cc.fillStyle = bak;

}


cc.fillText( this.definition.itemTitles[ i ],

gx, gy + this.appcfg.charsize - 2 );

}


cc.restore();


//debug. メニューの表示指定位置を示す赤い点

//(青いグリッドの交点と赤い点が一致している)

if( 1 ) {

cc.beginPath();

cc.arc( this.gx + this.padding, this.gy + this.padding, 2, 0, 6.28 );

cc.fillStyle = "red";

cc.fill();

}

}//draw()

//---Controller---

keyExec( key ) {

//キーを処理する(Menu)

if( key == 90 ) { //z key

//決定キーの挙動

this.sigotoOk( true );

/*

RPGイベントスクリプト(たとえばstoryOfCamp())内の

while( await menu.select() ) {...} のselect()の返値がこの引数。

trueは「メニュー選択が行われた」という意味

falseは「「メニュー選択がキャンセルされた」という意味

*/

}

else if( key == 88 ) { //x key

//キャンセルキーの挙動

this.selectedIndex = null;

this.selectedItemTitle = null;

this.selectedItem = null;

this.sigotoOk( false );

}

else {

//カーソルの移動

let addY = ( key == 40 ) - ( key == 38 );


if( addY ) {

this.cursorY += addY;

//check. 範囲外

if( this.cursorY < 0 )

this.cursorY = 0;

else if( this.cursorY >= this.definition.itemTitles.length )

this.cursorY = this.definition.itemTitles.length - 1;


this.updateSelectedIndex();

}//if

}//if

}//keyExec()

select() {

return new Promise( function( sigotoOk ) {

this.sigotoOk = sigotoOk;

}.bind( this ) );

/*

new Promise()の引数の関数はすぐに実行される。

その際に関数に渡される引数のsigotoOkは

Promiseの完了を知らせる関数である。

この関数(sigotoOk)はJavaScriptのシステムが自動的に引数として渡す。

sigotoOk()がどこかのタイミング(ここではkeyExec()内)で

実行されると、awaitで一時停止していたJavaScript(ここではstoryOfCamp())

が再開される。

*/

}//select()

}//Menu


//#####


</script>

<style>

.help, .help div {

position : absolute;

top : 0;

right : 0;

padding : .5em;

}

</style>

</head>

<body onload="onloadx()">

<canvas id="testCanvas" style="

object-fit : contain;

width : 100%;

height : 100%;

">

There is no canvas.

</canvas>


<div class="help" style="background-color:white; border:solid 3px #0f0;">

<div style="

background-color : #0f0;

border-radius : 0 0 0 1em;

cursor : pointer;

" onclick="this.parentNode.style.display = 'none';"> HELP × </div>

zキーまたはタッチ : 「決定」<BR>

xキーまたはダブルタッチ : 「キャンセル」<BR>

方向キーまたはフリック : その方向に1回動かします。<BR>

スワイプして止める : 方向キーを押し続ける動きになります。

</div>

</body>

</html>


RPG コマンドメニュー Step. 13

メニュー段組に対応

段組とは、「2列以上の列に分けて文字や図などを配列すること」です。

このようにドラクエ2のコマンドメニューと同じものを表現できます。

変更点は1~13ほどあります。大まかには、

  • 段組のための新しい設定値を用意
  • 上下だけでなく、左右にも動くようになったカーソルの対応
  • メニュー選択肢をマス目上に配置する

といったことを行っています。

269	baloonLength : .5, baloonShiftBottom : 4, baloonShiftRight : 8
<strong>変更点1 </strong> 目的:段組対応<br>
横方向の段組の数。
270 baloonReverse : true <strong>変更点2 </strong> 目的:段組対応<br> メニュー選択肢が流れる方向。<BR> 0: 右へ流れきったら下の行へ移るのか、<BR> 1: 下へ流れきったら右へ移るのか。
485 baloonShiftBottom : 8 <strong>変更点3 </strong> 目的:段組対応<br> 段組のためにいろいろ設定値が増えた。
各設定はコメントの通り。
501 <strong>変更点4 </strong> 目的:段組対応<br> 未指定の際、自動計算 rows
505 baloonLength : .5, baloonShiftBottom : 4 <strong>変更点5 </strong> 目的:段組対応<br> 未指定の際、自動計算 ch
513 <strong>変更点6 </strong> 目的:段組対応<br> 選択肢の中で一番長い文字数を得る
516 baloonLength : .5, baloonShiftBottom : 4 <strong>変更点7 </strong> 目的:段組対応<br> 未指定の際、自動計算 cellCW
521 baloonLength : .5, baloonShiftBottom : 4 <strong>変更点8 </strong> 目的:段組対応<br> 未指定の際、自動計算 cw
538 baloonLength : .5, baloonShiftBottom : 4 <strong>変更点9 </strong> 目的:段組対応<br> カーソルが横方向にも動くようになったため
546 <strong>変更点10 </strong> 目的:段組対応<br> カーソル位置に対応する配列 items, itemTitles の添え字を得る
601 <strong>変更点11 </strong> 目的:段組対応<br> 行と列を進めて、選択肢を描いていく 614 baloonLength : 0.5, baloonShiftBottom : 4 行と列がカーソル位置のときはカーソルを描画
617 カーソルの大きさを計算
624 描く選択肢がないなら次のループへ
664 <strong>変更点12 </strong> 目的:段組対応<br> カーソルは横にも動くようになったため
671 <strong>変更点13 </strong> 目的:段組対応<br> X, Y それぞれ、範囲外であれば訂正

以下の JavaScript を新しいウィンドウで実行

<html><!--ESCAPEPROCESS-->

<head>

<title>Step13</title>

<meta content="text/html; charset=UTF-8" http-equiv="content-type">

<script>

console.clear();

</script>

<script src="touchjs/touch.js"></script>

<script>


//#####


function onloadx() {

app = new App( document.getElementById( "testCanvas" ) );

app.start();

}

/*

皆の者、聞いてくれ。


魔王は、突然この世にあらわれ、

いくつかの国や街、村を滅ぼした。


もうすでにわが国の軍隊は何度も魔王に挑み、敗北している。


兵士たちよ、皆の者の中に、

われこそはと名乗りを上げ、

魔王を倒してくれる者はおらぬか?



名乗りを上げますか?

*/


//##### App #####


class App {

//---Model---

constructor( canvas ) {

this.cc = canvas.getContext( "2d", { alpha : false } );

this.cc.canvas.width = 512;

this.cc.canvas.height = 448;


//画面上の要素を目的別に参照するための配列群

//(1つの要素が複数の目的に属しても良い)

this.elementz = {

menus : new Array(), //メニュー要素

draws : new Array(), //描画できる要素

}


this.currentElement = this;

this.animationFrameId = null;

this.cfg = {

charsize : 16,

}


//---storyData


this.douguMihonz = {

"やくそう" : {

storyOfCamp : async function() {

let dousuruMenu = this.openMenu( {

title : "どうする",

itemTitles : [

"つかう",

"わたす",

"すてる",

],

items : null,

} );

while( await dousuruMenu.select() ) {

alert( dousuruMenu.selectedItemTitle );

}//while

this.closeMenu();

}.bind( this ),//storyOfCamp()

},//やくそう

}//douguMihonz


this.playerCharacterz = {

"キエルン" : {

dougus : [

{

name : "やくそう",

},

{

name : "やくそう",

},

{

name : "やくそう",

},

{

name : "ひほう",

},

],

},

"トンキー" : {

dougus : [

],

},

"リサリー" : {

dougus : [

{

name : "まほうのステッキ",

equipped : true,

//equipped(読み:イクウィップト、意味:装備中)

},

],

},

}//playerCharacterz


//tweak. requestAnimationFrameのために

this.frame = this.frame.bind( this );

/*

JavaScriptの

requestAnimationFrame()は関数を1つ受け取る。

class Appのframe()メソッドをその関数として渡した場合、

そのままだと

frame()メソッド中のthisがwindowオブジェクトになってしまう。

ここでbindしておくことでthisをあらかじめ定義する。

*/

}

start() {

//既存のキーイベントをバックアップ

this.onkeydownBak = window.onkeydown;

this.onkeyupBak = window.onkeyup;

//キーイベント

window.onkeydown = function( e ) {

//基本的にカレント要素に 押されたキー番号を渡す

this.currentElement.keyExec( e.which );

}.bind( this );

window.onkeyup = function( e ) {

}.bind( this );

//animationFrameの開始

this.frameTimestampBak = 0;

this.frame( 0 );


//タッチ対応 開始

touch.start( app.cc.canvas, 90, 88 );

}

stop() {

//タッチ対応 終了

touch.stop();


window.onkeydown = this.onkeydownBak;

window.onkeyup = this.onkeyupBak;

window.cancelAnimationFrame( this.animationFrameId );

}

frame( timestamp ) {

if( timestamp - this.frameTimestampBak >= 100 ) {

this.frameTimestampBak = timestamp;


this.draw( this.cc );

}

this.animationFrameId = requestAnimationFrame( this.frame );

}

openMenu( menuDefinition ) { //definition(意味:定義)

/*

メニューを開く手続き

Menuを作成し、目的別配列にセットし、カレントに設定する。

カレントとは、画面上に複数ある要素のうち、

キー入力等を受け取る現在アクティブな要素のこと。

*/

let menu = new Menu( menuDefinition, this );

//tweak. サブメニューである場合、適切な位置を自動設定する。

let parentMenu = this.elementz.menus[ this.elementz.menus.length - 1 ];

if( parentMenu ) {

menu.gx = parentMenu.gx;

menu.gy = parentMenu.gy;

//check. 親にタイトルがある場合、タイトル分下へ

if( parentMenu.definition.title ) menu.gy += this.cfg.charsize;


let tweakCx, tweakCy;

if( parentMenu.definition.ch / parentMenu.definition.cw < 1 ) {

//親メニューが横長

tweakCx = parentMenu.cursorX * parentMenu.definition.cellCw + parentMenu.cursorX + 1;

//式中の + parentMenu.cursorX は

//選択肢間の横方向スペース(spacing)の数を意味する。

tweakCy = parentMenu.cursorY + 1;

console.log( "サブメニュー位置決め", 1 );

} else {

//親メニューが縦長

tweakCx = parentMenu.definition.cw;

tweakCy = parentMenu.selectedIndex + 1;

console.log( "サブメニュー位置決め", 2 );

}


menu.gx += tweakCx * this.cfg.charsize;

menu.gy += tweakCy * this.cfg.charsize;


if( Utl.isContain( parentMenu, menu ) ) {

//見た目として子メニューが親メニュー内に含まれるとき

//少し右にはみ出させる

menu.gx = parentMenu.gx + parentMenu.gw - menu.gw + this.cfg.charsize;

console.log( "サブメニュー位置決め", 3 );

}

}//if


this.elementz.menus.push( menu );

this.elementz.draws.push( menu );

this.currentElement = menu;

return menu;

}

closeMenu() {

//一番手前のメニューを閉じます。


//menusから削除し、

//(menusの最後が一番手前のメニューであり、それを閉じたいからpopでよい)

let menu = this.elementz.menus.pop();

//そのmenuをdrawsからも削除する。

this.elementz.draws.splice( this.elementz.draws.indexOf( menu ), 1 );


//カレントについて

if( this.elementz.menus.length )

//1つ前のmenuにカレントを変更する。

this.currentElement = this.elementz.menus[ this.elementz.menus.length - 1 ];

else

//メニューが他に無いときはカレントはこのアプリとする。

this.currentElement = this;

}

//---View---

draw( cc ) {

cc.fillStyle = "black";

cc.fillRect( 0, 0, cc.canvas.width, cc.canvas.height );

cc.font = ( this.cfg.charsize - 1 ) + "px monospace";


//グリッド

cc.strokeStyle = "darkblue";

for( let y = 0; y < cc.canvas.width; y += this.cfg.charsize ) {

for( let x = 0; x < cc.canvas.width; x += this.cfg.charsize ) {

cc.strokeRect( x, y, this.cfg.charsize, this.cfg.charsize );

}

}


//「描画できる要素」を描画

for( let element of this.elementz.draws ) {

element.draw( cc );

}


//debug. グリッド2

if( 0 ) {

cc.strokeStyle = "RGBA(0,0,0,.125)";

for( let y = 0; y < cc.canvas.width; y += this.cfg.charsize ) {

for( let x = 0; x < cc.canvas.width; x += this.cfg.charsize ) {

cc.strokeRect( x, y, this.cfg.charsize, this.cfg.charsize );

}

}

}

}//draw()

//---Controller---

keyExec( key ) {

//キーを処理する(App)

if( key == 90 ) { //z key

this.storyOfCamp();

}

else if( key == 88 ) { //x key

}

else {

console.log( "app:", key );

}

}


//---DATA---

//RPGイベントスクリプト(=storyと呼ぶ)


async storyOfCamp() { //中で await を使う関数は async を付けること

//RPGイベントスクリプト:

//フィールド上で開くコマンドメニュー

let commandMenu = this.openMenu( { //トップメニュー作成

cx : 3,

cy : 3,

cols : 2,

masuDir : 1, //1:下へ、0:右へ

title : "コマンド",

itemTitles : [

"しらべる",

"どうぐ",

"まほう",

"ひっさつ",

"つよさ",

"みせ(テスト)",

"Sample2",

],

items : null,

} );

while( await commandMenu.select() ) { //トップメニュー選択待ち

/*

awaitでJavaScriptは一時停止します。

commandMenu.select()はPromiseをreturnしています。

そのPromiseの「処理完了」で一時停止が解除されます。


なぜwhileなのかというとそれはサブメニューに対応するためです。

whileを入れ子にするとサブメニューを表現しやすくなります。


async, await, Promise は少し難しいですが

ゲームでよく使うhitAnyKey()や、delay()を

簡単に実現できて便利なので覚えたほうが良いです。

*/

if( commandMenu.selectedItemTitle == "どうぐ" ) {

let characterMenu = this.openMenu( { //サブメニュー1作成

title : "だれの",

itemTitles : Object.keys( this.playerCharacterz ),

items : Object.values( this.playerCharacterz ),

} );


while( await characterMenu.select() ) { //サブメニュー1選択待ち

let character = characterMenu.selectedItem;

//check. どうぐをもっていない

if( character.dougus.length == 0 ) {

continue;

}


//どうぐリストに装備中を示すeの文字を付与する

let itemTitles;

let douguNames = Utl.getArrayOfKey( character.dougus, "name" );

let douguEquippedMarks = new Array();

//eの文字の有無を表す配列を作成

for( let i = 0; i < character.dougus.length; i++ )

douguEquippedMarks.push( character.dougus[ i ].equipped ? "e" : "" );

//どうぐの名前とeの文字を合成してメニューの選択肢にする

itemTitles = Utl.joinToRight( douguNames, douguEquippedMarks );


let douguMenu = this.openMenu( { //サブメニュー2作成

title : "どれ",

itemTitles : itemTitles,

items : character.dougus,

} );

while( await douguMenu.select() ) { //サブメニュー2選択待ち

let douguMihon = this.douguMihonz[ douguMenu.selectedItem.name ];

//check. 未定義

if( ! douguMihon ) continue;

if( ! douguMihon.storyOfCamp ) continue;

await douguMihon.storyOfCamp( characterMenu.selectedItem );

}

this.closeMenu();

}

//characterMenu.select()でキャンセルされるとここに来ます。

this.closeMenu();

}

else if( commandMenu.selectedItemTitle == "みせ(テスト)" ) {

this.storyOfShop( {

title : "どうぐや「セリーヌ・デ・イオン」",

shopItems : [

{ name : "やくそう", price : 10 },

{ name : "どうのつるぎ", price : 120 },

],

} );

}

}//while

//commandMenu.select()でキャンセルされるとここに来ます。

this.closeMenu();

}//storyOfCamp()

async storyOfShop( menuDefinition ) {


//shopItems の各要素の name と price を結合し、1つのメニュー選択肢にする

let names = Utl.getArrayOfKey( menuDefinition.shopItems, "name" );

let prices = Utl.getArrayOfKey( menuDefinition.shopItems, "price" );

prices = Utl.hans2zens( prices );

menuDefinition.itemTitles = Utl.joinToRight( names, prices, menuDefinition.title.length, "G" );


menuDefinition.items = menuDefinition.shopItems;


//お店メニューを実行

let shopMenu = this.openMenu( menuDefinition );

while( await shopMenu.select() ) {

let shopItem = shopMenu.selectedItem;

alert( shopItem.name + "は " + shopItem.price + " Gだ。" );

}

this.closeMenu();

}//storyOfShop()

}//App


//##### Utl #####


class Utl {

/*

汎用のユーティリティを集めたクラス。

このクラスの各メソッドはクラスメソッド(static を付けている)なので、

alert( Utl.han2zen( "test" ) ); //結果:"test"

のような書き方で、使用します。

*/

static getArrayOfKey( array, key ) { //keyに対応する値の配列を取得します。

/*

引数arrayの各要素はオブジェクトであることが前提です。

引数arrayの各要素のkeyプロパティからなる配列を返します。

*/

let result = new Array();

for( let object of array ) {

result.push( object[ key ] );

}

return result;

}

static han2zen( string ) { //文字列中の半角文字を全角文字に変換します。

//check.

string = String( string ); //数値の場合は文字列にする


let result = "";

let hanSta = "!".charCodeAt( 0 ); //文字コード中の半角開始位置

let hanEnd = "~".charCodeAt( 0 ); //文字コード中の半角終了位置

for( let i = 0; i < string.length; i++ ) {

let charCode = string.charCodeAt( i );

if( charCode >= hanSta && charCode <= hanEnd )

//半角は全角に

result += String.fromCharCode( charCode + 0xFEE0 );

else

//全角はそのまま

result += String.fromCharCode( charCode );

}

return result;

//上記は下記でも同じ結果となります。(正規表現とアロー関数式を使用)

//return s.replace( /[!-~]/g, c => String.fromCharCode( c.charCodeAt( 0 ) + 0xFEE0 ) );

}

static hans2zens( array ) { //配列中の半角文字を全角文字に変換します。

let result = new Array();

for( let element of array )

result.push( this.han2zen( String( element ) ) );

return result;

}

static getMaxLengthOf( array ) { //文字列の配列の中で、一番長い文字列の文字数を返す。

let maxLength = 0;

for( let element of array ) {

maxLength = Math.max( maxLength, String( element ).length );

}

return maxLength;

}

static joinToRight( array, notes, otherLength, unit ) { //文字列配列にメモを付ける(右寄せ)

/*

arrayの要素を左寄せ、notesの要素を右寄せにした文字列配列を返す。

arrayとnotesは配列であり、両者の数は等しいこと。

otherLength maxLengthについてotherLengthのほうが長いなら、otherLengthの長さを採用する。

unit noteに単位を付ける。

*/

//check. 引数が省略された

if( ! otherLength ) otherLength = 0;

if( ! unit ) unit = "";

//check. 間違えて文字列を指定している

if( typeof otherLength === "string" ) alert( "error at joinToRight()" );

//check. notes がすべて空文字列である

if( notes.join("") == "" ) return array;


let maxLength = this.getMaxLengthOf( array ) + 1 + this.getMaxLengthOf( notes ) + unit.length;

//途中の1は、少なくとも1つのスペースを開けるという意味


//check. otherLengthも考慮する

maxLength = Math.max( maxLength, otherLength );


let result = new Array();

for( let i = 0; i < array.length; i++ ) {

let element = array[ i ];

let note = notes[ i ];

let interval = maxLength - element.length - note.length - unit.length;

let spaces = Array( interval + 1 ).join( " " );

result.push( element + spaces + note + unit );

}

return result;

}//joinToRight()

static isContain( parent, child ) {

/*

parentの描画範囲がchildの描画範囲を含むときtrue

parent, child はそれぞれ、gx, gy, gw, gh を用意していることが前提。

*/

//check.

if( parent.gx == null || child.gx == null ) {

alert( "err: object has not gx or other axis at Utl.isContain()." );

return;

}

return child.gx >= parent.gx && child.gy >= parent.gy

&& child.gx + child.gw <= parent.gx + parent.gw

&& child.gy + child.gh <= parent.gy + parent.gh;

}//isContain()

}//Utl


//##### Menu #####


class Menu {

/*

Menu は、

「1つのメニューを表示し、カーソルで選択肢を選び、選ばれた選択肢を返す」

という単純な動きを行う。

*/


//---Model---

constructor( definition, app ) { //definition(意味:定義)

this.definition = Object.assign( { //assign()は第1引数を第2引数で上書きする

//初期値

cx : 0,

cy : 0,

cols : 1, //選択肢を横方向に並べる数

masuDir : 1, //1:下へ、0:右へ

spacing : 1, //選択肢同士の左右方向のスペース(単位=文字)

cellCh : 1, //選択肢の高さ(単位=文字)

}, definition ); //初期値に上書きする

/*

その他

cw //ウィンドウの横幅(単位=文字)

ch //ウィンドウの縦幅(単位=文字)

rows //選択肢を縦方向に並べる数

cellCw //選択肢の幅(単位=文字)

*/


this.appcfg = app.cfg;


//check. rows 未指定時

if( this.definition.rows == null ) {

this.definition.rows = Math.ceil( this.definition.itemTitles.length / this.definition.cols );

}

//check. ch 未指定時

if( this.definition.ch == null ) {

this.definition.ch = this.definition.rows * this.definition.cellCh;

}

//check.

if( this.definition.title )

this.definition.ch ++;


//選択肢の中で一番大きい文字列幅を得る。

let maxLength = Utl.getMaxLengthOf( this.definition.itemTitles );


//check. cellCw 未指定時 自動計算

if( this.definition.cellCw == null ) {

//maxLength を横幅とする

this.definition.cellCw = maxLength;

}

//check. cw 未指定時 自動計算

if( this.definition.cw == null ) {

this.definition.cw = maxLength * this.definition.cols + ( this.definition.cols - 1 ) * this.definition.spacing;

//check. タイトルについても考慮する

if( this.definition.title )

this.definition.cw = Math.max( this.definition.cw, this.definition.title.length );

}


//メニュー枠の内側のスペース(単位dot)

this.padding = this.appcfg.charsize / 2;


//画面上の描画位置とサイズ

this.gx = this.definition.cx * this.appcfg.charsize - this.padding;

this.gy = this.definition.cy * this.appcfg.charsize - this.padding;

this.gw = this.definition.cw * this.appcfg.charsize + this.padding * 2;

this.gh = this.definition.ch * this.appcfg.charsize + this.padding;


//カーソル位置

this.cursorX = 0;

this.cursorY = 0;


//選択された選択肢番号と選択肢

this.updateSelectedIndex();

}//constructor()

updateSelectedIndex() {

let index;

if( this.definition.masuDir ) {

//下へ

index = this.cursorY + this.cursorX * this.definition.rows;

} else {

//右へ

index = this.cursorX + this.cursorY * this.definition.cols;

}

//check. 選択項目が空欄

if( index >= this.definition.itemTitles.length ) {

this.selectedItemTitle = "";

this.selectedItem = null;

return;

}


this.selectedIndex = index;

this.selectedItemTitle = this.definition.itemTitles[ this.selectedIndex ];

if( this.definition.items )

this.selectedItem = this.definition.items[ this.selectedIndex ];

else

this.selectedItem = null;

}

//---View---

draw( cc ) {


cc.save();


cc.translate( this.gx, this.gy + this.padding ); //tr1


//枠を描画

cc.fillStyle = "white";

cc.strokeStyle = "black";

cc.fillRect( 0, 0, this.gw, this.gh );

cc.strokeRect( 0, 0, this.gw, this.gh );


//タイトルを描画

if( this.definition.title ) {

let leftSpace = Math.floor( ( this.definition.cw - this.definition.title.length ) / 2 );

let titleStringX = this.padding + leftSpace * this.appcfg.charsize;

let titleStringY = this.appcfg.charsize - 2;

let titleW = this.gw;

let titleH = this.appcfg.charsize;

//タイトル背景

cc.fillStyle = "blue";

cc.fillRect( 0, 0, titleW, titleH );

//タイトル文字

cc.fillStyle = "white";

cc.fillText( this.definition.title, titleStringX, titleStringY );

cc.fillText( this.definition.title, titleStringX + 1, titleStringY );


cc.translate( 0, this.appcfg.charsize ); //tr2

}


//選択肢を描画

cc.translate( this.padding, 0 ); //tr3

cc.fillStyle = "black";

for( let col = 0; col < this.definition.cols; col++ ) {

for( let row = 0; row < this.definition.rows; row++ ) {

let i;

if( this.definition.masuDir ) {

//下へ

i = row + col * this.definition.rows;

} else {

//右へ

i = col + row * this.definition.cols;

}

let gx = ( col * this.definition.cellCw + ( col > 0 ) * this.definition.spacing ) * this.appcfg.charsize;

let gy = row * this.definition.cellCh * this.appcfg.charsize;

//check. カーソルを描画

if( col == this.cursorX && row == this.cursorY ) {

let bak = cc.fillStyle;

cc.fillStyle = "#ff7";

let gw = this.definition.cellCw * this.appcfg.charsize;

let gh = this.definition.cellCh * this.appcfg.charsize;

cc.fillRect( gx, gy, gw, gh );

cc.fillStyle = bak;

}


//check.

if( i >= this.definition.itemTitles.length ) continue;


cc.fillText( this.definition.itemTitles[ i ],

gx, gy + this.appcfg.charsize - 2 );

}

}


cc.restore();


//debug. メニューの表示指定位置を示す赤い点

//(青いグリッドの交点と赤い点が一致している)

if( 1 ) {

cc.beginPath();

cc.arc( this.gx + this.padding, this.gy + this.padding, 2, 0, 6.28 );

cc.fillStyle = "red";

cc.fill();

}

}

//---Controller---

keyExec( key ) {

//キーを処理する(Menu)

if( key == 90 ) { //z key

//決定キーの挙動

this.sigotoOk( true );

/*

RPGイベントスクリプト(たとえばstoryOfCamp())内の

while( await menu.select() ) {...} のselect()の返値がこの引数。

trueは「メニュー選択が行われた」という意味

falseは「「メニュー選択がキャンセルされた」という意味

*/

}

else if( key == 88 ) { //x key

//キャンセルキーの挙動

this.selectedIndex = null;

this.selectedItemTitle = null;

this.selectedItem = null;

this.sigotoOk( false );

}

else {

//カーソルの移動

let addX = ( key == 39 ) - ( key == 37 );

let addY = ( key == 40 ) - ( key == 38 );


if( addX || addY ) {

this.cursorX += addX;

this.cursorY += addY;

//check. 範囲外

if( this.cursorX < 0 )

this.cursorX = 0;

else if( this.cursorX >= this.definition.cols )

this.cursorX = this.definition.cols - 1;

if( this.cursorY < 0 )

this.cursorY = 0;

else if( this.cursorY >= this.definition.rows )

this.cursorY = this.definition.rows - 1;


this.updateSelectedIndex();

}//if

}//if

}//keyExec()

select() {

return new Promise( function( sigotoOk ) {

this.sigotoOk = sigotoOk;

}.bind( this ) );

/*

new Promise()の引数の関数はすぐに実行される。

その際に関数に渡される引数のsigotoOkは

Promiseの完了を知らせる関数である。

この関数(sigotoOk)はJavaScriptのシステムが自動的に引数として渡す。

sigotoOk()がどこかのタイミング(ここではkeyExec()内)で

実行されると、awaitで一時停止していたJavaScript(ここではstoryOfCamp())

が再開される。

*/

}//select()

}//Menu


//#####


</script>

<style>

.help, .help div {

position : absolute;

top : 0;

right : 0;

padding : .5em;

}

</style>

</head>

<body onload="onloadx()">

<canvas id="testCanvas" style="

object-fit : contain;

width : 100%;

height : 100%;

">

There is no canvas.

</canvas>


<div class="help" style="background-color:white; border:solid 3px #0f0;">

<div style="

background-color : #0f0;

border-radius : 0 0 0 1em;

cursor : pointer;

" onclick="this.parentNode.style.display = 'none';"> HELP × </div>

zキーまたはタッチ : 「決定」<BR>

xキーまたはダブルタッチ : 「キャンセル」<BR>

方向キーまたはフリック : その方向に1回動かします。<BR>

スワイプして止める : 方向キーを押し続ける動きになります。

</div>

</body>

</html>



(訪問者のどんなニーズと この記事がつながるか)


2022/4/10(日)

イラストAC にイラスト投稿

母の日に贈るプレゼントを買うのに、もう少しお金が必要だと思って、イラストAC にイラストを投稿しました。

マウスのイラストです。画像リンクのリンク先はイラスト AC の私のプロフィールページです。


(訪問者のどんなニーズと この記事がつながるか)


2022/4/9(土)

「ザ・トリロジーズ -T&E SOFT / XTAL SOFT COLLECTION-」届きました

2021年春発売予定だった「ザ・トリロジーズ -T&E SOFT / XTAL SOFT COLLECTION-」(税込13,530円)が今日やっと届きました。

発売予定から1年遅れでした。だいぶ待ちましたね。

ではどんな品物なのか、開封から見ていきましょうかね。

ちなみに私がこれを購入した動機は、

『ファミコン版 ドラゴンクエスト1 が開発されたときに 大いに参考にされた RPG の1つが「夢幻の心臓」であり、

                     「夢幻の心臓」を体験することで RPG の面白さの秘密を探りたい!』

です。

さぁ、休職して収入が止まっていますが、せっかくだから張り切っていきましょう!

(以降、写真とコメントの組が全部で22組あります)


fig.
▲届いた状態。段ボール

1/22

届いた段ボールの状態です。

右下に10円玉。サイズを確認してください。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲取り出すと、プチプチ巻き

2/22

段ボールから取り出したところ。

プチプチにくるまれています。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲感動のプラスチックケース!

3/22

プチプチをはがして…

あっこれは!

この感覚は!

でっかくて、ずっしりと重くて、きれいな表紙と、裏面はゲームの楽しい紹介!すごく豪華な感じ!

なんて懐かしい。昔のゲームソフトってこうなんだよね!

左の画像リンクをクリックすると 画像を拡大 します。


fig.
▲EGG の「ザ・トリロジーズ」のページの記載

30年前の忘れていたこの感覚。また再び味わえるとは、これが D4 エンタープライズ、PROJECT EGG のやりたかったことなんだ。

この "プラスチックケース" がコロナウイルスのために海外からの生産・輸入が止まっていて、それでも妥協せず、待ちに待って、そして意図したとおりの このご提供。どうもありがとう。良い仕事しているね。



fig.
▲その側面

4/22

側面はこう。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲シュリンクと紙を外したところ

5/22

そしてシュリンクラップ(透明フィルム)をはがすと、トリロジーズのロゴは別の紙で、下にはハイドライドのロゴが!

私は「ハイドライド」を選びましたが、何を選んだかによって EGG はこのパッケージを変えて出荷してくれるんです。

人によっては、当時「ハイドライド」の購入で、その豪華さを味わっていたし、

人によっては、「夢幻の心臓」だったかもしれない。

または「スターアーサー伝説 -惑星メフィウス-」だったのかもしれない。

そこを選んでもらうことで、その人その人の昔のあの感覚を、段ボールを開けてプチプチをはがしたその瞬間に味わってもらおうという、そういう仕掛けだったんです。すごいなぁ EGG。職人、仕掛人だ。その仕事に感動だ。商品もそうだけど、その仕事にも感動させられる。2重の感動。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲裏面。当時のままの、ハイドライドの紹介!

6/22

プラスチックケースの裏面。

当時のハイドライドのパッケージそのまま!

(私は本当は「ハイドライド」じゃなくて、「ハイドライド3」でした。近いということで「ハイドライド」を選びました)

40代後半で、身体が疲れていると、涙もろくなっていて、ちょっとうるんじゃうなぁ。。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲プラケースを開けると冊子群。

7/22

パッケージを開けるとこう。

EGG によれば、「当時のマニュアルを可能な限り再現!」しているんだそうです。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲ハイドライドの当時のマニュアル

8/22

まずは「ハイドライド」のマニュアル。

私はハイドライドは MSX 版を誰かからもらって、カートリッジだけを持っていました。

土曜日に1日でクリアしていた覚えがあります。

「1日で」というのはセーブ機能がなかったから1日でクリアするしかなかったんです(たしか)。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲夢幻の心臓の当時のマニュアル

9/22

これが本命、「夢幻の心臓」のマニュアル。

本当の本命は「無限の心臓2」のほうかな?ドラクエ1のモチーフになったゲームです。

夢幻の心臓シリーズを作った生みの親の 富 一成 氏は、XTALソフトを退社した後、日本ファルコムに入社し、「ダイナソア」を作ったんだそうです。

たしかに両ソフトの重厚なストーリーは共通してますね。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲クリムゾンの当時のマニュアル

10/22

「クリムゾン」

クリムゾンは私の当時の友達が MSX 版を持っていました。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲ルーンワースの当時のマニュアル

11/22

「ルーンワース -黒衣の貴公子-」

私は高校生のころ「ルーンワース2」のほうを買ったんだったかな。

ゲームのシステムが、プログラム的に洗練されているな、と感じながら遊んでいました。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲スターアーサー1の当時のマニュアル

12/22

「スターアーサー伝説 -惑星メフィウス-」

「スターアーサー」というキーワードで私が知っているのは、

  • レーザーディスクゲームでそういうのがある。
    ビデオの映像でゲームになっているってどういうことなんだろう…と思っていた。
  • T&E SOFT の「ハイドライド3」に付属していた 20thAnniversary のカセットテープの中に収録されていた同ゲームのアレンジ楽曲。それが壮大だった。

の2つの情報でしか知りませんでした。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲「T&Eマガジン27号」が付属!

13/22

冊子「新T&Eマガジン」。

「T&E」って何の略だろうと30年前に思っていましたが、この冊子のインタビュー記事の社長、副社長の名前を見てわかりました。兄弟の名前の頭文字だったんですね。俊郎&英二。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲ゲームパッケージのカード化

14/22

各ゲームのパッケージをカードにしたもの。

私が左手に持っている「ハイドライド3」、なつかしいな。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲その裏面

15/22

そのカードの裏面は、また別のゲームのパッケージ。

写真の右から2番目の黄色い「ルーンワース2」のパッケージは買ったので覚えています。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲T&Eマガジンの表紙のカード化

16/22

T&E マガジンの表紙のカード。

私は「ハイドライド」の表紙を選んだので、それに付随してT&E マガジンの1~13号の表紙です。

カードの片面は1から7号で、

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲その裏面

17/22

裏を見ると8から13号。加えて「新T&Eマガジン」の27号もカード化してるな…。

なお、「ハイドライド」以外を選ぶと、14号から26号の表紙だそうです。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲冊子の次には ROM が!(CD-ROM)

18/22

以上冊子群を取り除くと、そこにはソフトウェアが!

昔はそれが ROM カートリッジだったり、フロッピーディスクだったり。

今回は、CD-ROM ですね。

昔のゲームは容量が小さいので、DVD-ROM 必要なしってことですね。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲CD-ROM、音楽CD、CD-ROM

19/22

右が「GAME DISC」(CD-ROM)

中央が「SOUND SELECTION」(音楽CD2枚組)

左が初回特典「SHOOTING COLLECTION」(CD-ROM)

初回特典には、当時憧れだった「レイドック」各種が入っています。

電気屋さんのショーケースの一番高いところに置いてあってタイトルだけを見て面白そうだと眺めていました。

どんな感じか楽しみだな。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲その裏面

20/22

それぞれの裏面。

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲プラケースのジャケット

21/22

最後にプラスチックケースのジャケットを取り出すと、2枚入っていました。

手前が「ハイドライド」

奥が「夢幻の心臓」

左の画像リンクをクリックすると 画像を拡大 します。



fig.
▲その裏面。リバーシブル

22/22

その裏面。

手前が、今回の「ザ・トリロジーズ」

奥が「スターアーサー伝説 -惑星メフィウス-」

合計4つのパッケージを楽しめます。

左の画像リンクをクリックすると 画像を拡大 します。



以上、22組の写真とコメント、長かったのに読んでもらってありがとうございました。

まだソフトウェアを動かしていませんが、パッケージ内容だけ紹介しました。

以上の紹介だと、ソフトウェアは

だけに見えますが、実際は、それぞれのタイトルの続編やその他機種版、さらに動画や各種 PDF など、色とりどり満載になっています。

(初回特典の期間(たぶん初回生産分の販売完了)を過ぎると、シューティングゲームの CD は付かなくなるようです)

感動をどうもありがとう。D4エンタープライズさん。

それから

かなり前から気になっていたんですが、EGG の「ザ・トリロジーズ」のページの最初のニュースの欄に、

>2021.02.05
   >【追加】PC-6001mkII版「スターアーサー伝説」3部作、PC-8801版「惑星メフィウス」、コンシューマ版「ハイドライド3」収録決定!

と書かれていて、ページの後のほうのタイトル別の表示には、

このように、「CS」(コンシューマー)と表示されています。

これが、「ファミコン」なのか、「MSX2」なのか、「メガドライブ」なのかという区別がつかないんです。

どれなんでしょうね…インストールしてみればわかることですが…

私が過去に購入した MSX2 版だと懐かしくてうれしいんですが…。ファミコンのような気がする。


で、イントーラーの「お読みください」のボタンを押して、テキストエディタで開かれたテキストを見ると…

『ハイドライド3』(FC版)

とありますね。やっぱりか! MSX2 だったらおいしすぎた。


(訪問者のどんなニーズと この記事がつながるか)


2022/4/4(月)

指輪物語

映画にもなった「ロード・オブ・ザ・リング」の原作小説のほう「指輪物語」(The Load of the Rings)を読んでいて、「文字だけの描写ではよくわからないなぁ」と思って、自分で挿絵を描きました(まぁ適当なものですが)。

下の写真はクリックすると挿絵部分をクローズアップします。

(右側のフキダシ)ピピン: おい!おい!ぼくは何にもしやしないぞ。ちょっと通してくれ、いいだろ!

(上のフキダシ)メリー: ぼくが君なら怒鳴らないよ

(左下のフキダシ)フロド: 3人を古森に連れて来て正しかっただろうか…

下部のカッコ書き: 〔4人のホビットたち、子馬に乗り、いとわしい古森を進むが…〕

こうやって挿絵を自分で作ると、物語が見えてくる感じがします。


指輪物語はもともとは児童向けの物語だったそうです。

だからなのか、登場人物の名前はピピンとかメリーとかどこかコミカルになっています。

ピピン: フロドの親戚で、いとこの子供。本名はペレグリン・トゥックと言い、名家の息子。

メリー: 同じく、フロドの親戚で、いとこの子供。本名はメリアドク・ブランディバックと言い、優れた人格でたくましい。

サム: フロドとは家系としてはあまりつながりがないが、フロドの家の庭師だった。本名サムワイズ・ギャムジーと言い、お人よし。

フロド: 指輪物語の主人公。本名フロド・バギンズと言い、母のいとこにあたるビルボ・バギンズから指輪を受け取った。


(訪問者のどんなニーズと この記事がつながるか)


休職

半年前ぐらいから体調が悪く、3月10日くらいからひどくなりました。

仕事の作業を始めると、ふらふらとし、息切れがあり、貧血気味で、倒れそう。

そのためこれまで仕事をお休みしていましたが、明日から休職になってしまいました。

とりあえず、4月いっぱい休職します。

時期過去の仕事内容詳細
今から13年前 カラオケボックス
店長業務
5年間
カラオケボックスのチェーン店で雇われ店長をしていました。
低予算の事業だったため、極力人件費を削った結果、店内には私一人でいることが多く、
フロント、厨房、客室、店内のすべての仕事を一人で駆け回ってこなしていました。
骨の髄までエネルギーをしぼって動いていたので、ダメージが大きかった。痛恨の一撃。
それでもやりがいがあり、楽しい仕事だったので悔いはありません。
今から8年前 電子回路関係の会社
4年間
電車の制御基板を交換する仕事。
電車は日本全国にあるため、基本的に日本全国どこへでも出張して作業します。
たとえば、電車が全部で80本あり、1日1本ずつ、基板を交換。
そのためホテルに泊まりこんで2か月くらいの出張になりました。
しかも電車は昼間はお客さんを乗せていて作業ができないため、ホテルから出て夜10時に出勤、翌朝8時にホテルに帰る、という内容でした。
1か月間休みが無いときもあり、毎日体中にシップを貼り、同業者の方から「顔色悪いけど大丈夫??」と聞かれたりしていました。
そういう出張が4,5回くらいあったと思います。痛恨の一撃。
こちらもやりがいがあったので、悔いはありません。
今から4年前 今の会社
4年間
大企業の工場のライン作業。
毎日時間に追われ、大量の作業をとどこおりなくこなすのに四苦八苦。痛恨の一撃。
ミスが多くて信用を無くしている側面もあるものの、
まじめだ、といろいろな方から評価してもらえているし、通常会社から指示されて受検する「電子機器組み立て」という資格も派遣なのに自発的に受検し2級合格しているということで、「そんな人珍しい」と びっくりされ、喜ばれてもいます。
そして1級の学科は合格しているので、あとは実技試験だけで、2022年は今まさに受検の申請書を書いたところです。
なので、まだまだやる気いっぱいです。

そんな感じで、身体を痛めつけてきました。

あと、2年前に歯医者で上の歯を抜歯した際、その抜歯の穴と、鼻の穴が貫通してしまい、口の中の雑菌が鼻の中へと入っていき、「歯性上顎洞炎(しせい じょうがくどう えん)」(いわゆる蓄膿症)になってしまいました。2年間耳鼻科で薬をもらって治療してきましたが直らず、今月4/19から大きな病院で7日間入院し手術します。その病院の先生によると、ふつうの蓄膿症は薬で直るけど、歯医者から始まった蓄膿症は(菌が違うから?)薬では治らないよ、手術になるよとのこと。体調の悪さが、この鼻の病気から来ているのではと素人的に疑っています。

さて、、いろいろ始めよう。いつやるの、今でしょ。

ナントカナルサー。ナンクルナイサー

Nantoca Nalucer.

Nancle Nicer.

うーん、大丈夫なんだろうか。


(訪問者のどんなニーズと この記事がつながるか)